﻿using System.Linq;
using System.Threading;
using FinMkts.Messaging.Pgm;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Net.Sockets;
using System.Net;

namespace FinMkts.Messaging.UnitTests
{
    [TestClass()]
    public class RmSocketExtensionsTest
    {
        private static readonly IPEndPoint MulticastEndPoint = new IPEndPoint(IPAddress.Parse("234.7.8.9"), 40000);
        private static readonly IPEndPoint AnyEndPoint = new IPEndPoint(IPAddress.Any, 0);
        public TestContext TestContext { get; set; }

        Socket CreatePgmSocket()
        {
            return new Socket(AddressFamily.InterNetwork, SocketType.Rdm, RmConstants.ProtocolType);
        }

        [TestMethod, ExpectedException(typeof (ArgumentNullException))]
        public void GetReceiverStats_throws_forNullArgument()
        {
            RmSocketExtensions.GetReceiverStats(null);
        }

        [TestMethod, ExpectedException(typeof(SocketException))]
        public void GetReceiverStats_throwsSocketExecption_beforeBind()
        {
            using (Socket socket = CreatePgmSocket())
            {
                RmSocketExtensions.GetReceiverStats(socket);
            }
        }

        [TestMethod, ExpectedException(typeof(SocketException))]
        public void GetReceiverStats_throwsSocketExecption_beforeConnect()
        {
            using (Socket socket = CreatePgmSocket())
            {
                socket.Bind(MulticastEndPoint);
                RmSocketExtensions.GetReceiverStats(socket);
            }
        }

        [TestMethod]
        public void GetReceiverStats_returns_afterConnect()
        {
            using (var wait = new ManualResetEvent(false))
            using (Socket receivingSocket = CreatePgmSocket())
            {
                receivingSocket.Bind(MulticastEndPoint);
                receivingSocket.Listen(1);
                receivingSocket.BeginAccept(ar =>
                                                {
                                                    using (var accepted = receivingSocket.EndAccept(ar))
                                                    {
                                                        var stats = RmSocketExtensions.GetReceiverStats(accepted);
                                                        Assert.IsNotNull(stats);
                                                        wait.Set();
                                                    }
                                                }, null);

                using (Socket sendingSocket = CreatePgmSocket())
                {
                    sendingSocket.Bind(AnyEndPoint);
                    sendingSocket.Connect(MulticastEndPoint);
                    sendingSocket.Send(new byte[1]);
                }

                Assert.IsTrue(wait.WaitOne(TimeSpan.FromSeconds(0.5)), "timed-out");
            }
        }

        [TestMethod, ExpectedException(typeof (ArgumentNullException))]
        public void GetSendWindow_throws_forNullArgument()
        {
            RmSocketExtensions.GetSendWindow(null);
        }

        [TestMethod, ExpectedException(typeof(SocketException))]
        public void GetSendWindow_throwsSocketExection_beforeBind()
        {
            using (Socket socket = CreatePgmSocket())
            {
                RmSocketExtensions.GetSendWindow(socket);
            }
        }

        [TestMethod]
        public void GetSendWindow_returnsDefaults_beforeConnect()
        {
            using (Socket socket = CreatePgmSocket())
            {
                socket.Bind(AnyEndPoint);
                var window = RmSocketExtensions.GetSendWindow(socket);
                Assert.IsNotNull(window);
                Assert.IsTrue(window.RatePerSec.Bytes > 0);
                Assert.IsTrue(window.WindowLength > TimeSpan.Zero);
                Assert.IsTrue(window.WindowSize.Bytes > 0);
            }
        }

        [TestMethod]
        public void GetSendWindow_returnsDefaults_afterConnect()
        {
            using (Socket socket = CreatePgmSocket())
            {
                socket.Bind(AnyEndPoint);
                socket.Connect(MulticastEndPoint);
                var window = RmSocketExtensions.GetSendWindow(socket);
                Assert.IsNotNull(window);
                Assert.IsTrue(window.RatePerSec.Bytes > 0);
                Assert.IsTrue(window.WindowLength > TimeSpan.Zero);
                Assert.IsTrue(window.WindowSize.Bytes > 0);
            }
        }

        [TestMethod, ExpectedException(typeof (ArgumentNullException))]
        public void GetSenderStats_throws_forNullArgument()
        {
            RmSocketExtensions.GetSenderStats(null);
        }

        [TestMethod, ExpectedException(typeof(SocketException))]
        public void GetSenderStats_throwsSocketExecption_beforeBind()
        {
            using (Socket socket = CreatePgmSocket())
            {
                RmSocketExtensions.GetSenderStats(socket);
            }
        }

        [TestMethod, ExpectedException(typeof(SocketException))]
        public void GetSenderStats_throwsSocketExecption_beforeConnect()
        {
            using (Socket socket = CreatePgmSocket())
            {
                socket.Bind(AnyEndPoint);
                var stats = RmSocketExtensions.GetSenderStats(socket);
            }
        }

        [TestMethod]
        public void GetSenderStats_returns_afterConnect()
        {
            using (Socket socket = CreatePgmSocket())
            {
                socket.Bind(AnyEndPoint);
                socket.Connect(MulticastEndPoint);
                var stats = RmSocketExtensions.GetSenderStats(socket);
                Assert.IsNotNull(stats);
                Assert.IsTrue(stats.BufferSpaceAvailable.Bytes > 0);
            }
        }

        [TestMethod]
        public void GetSenderStats_returnsWithUpdatedCount_afterSend()
        {
            using (Socket socket = CreatePgmSocket())
            {
                socket.Bind(AnyEndPoint);
                socket.Connect(MulticastEndPoint);
                socket.Send(new byte[1]);
                Thread.Sleep(300);  // stats are not updated immediatly
                var stats = RmSocketExtensions.GetSenderStats(socket);
                Assert.IsNotNull(stats);
                Assert.AreEqual(1ul, stats.ClientDataSent.Bytes);
                Assert.IsTrue(stats.TotalDataSent.Bytes > 0);
                Assert.IsTrue(stats.TotalODataPacketsSent > 0);
            }
        }

        [TestMethod, ExpectedException(typeof (ArgumentNullException))]
        public void GetSocketOption_throws_forNullArgument()
        {
            RmSocketExtensions.GetSocketOption(null, RmSocketOptionName.AddReceiveInterface);
        }

        [TestMethod, ExpectedException(typeof(SocketException))]
        public void GetSocketOption_throwSocketExecption_beforeBind()
        {
            using (Socket socket = CreatePgmSocket())
            {
                var option = RmSocketExtensions.GetSocketOption(socket, RmSocketOptionName.HighSpeedIntranet);
                Assert.IsNotNull(option);
                Assert.AreEqual(false, option);
            }
        }

        [TestMethod()]
        public void GetSocketOption_returnDefaultZero_forHighSpeedIntranetAfterBind()
        {
            using (Socket socket = CreatePgmSocket())
            {
                socket.Bind(AnyEndPoint);
                var option = RmSocketExtensions.GetSocketOption(socket, RmSocketOptionName.HighSpeedIntranet);
                Assert.IsNotNull(option);
                Assert.AreEqual(0, option);
            }
        }

        [TestMethod, ExpectedException(typeof (ArgumentNullException))]
        public void SetSendWindow_throws_forNullSocketArgument()
        {
            RmSocketExtensions.SetSendWindow(null, new RmSendWindow());
        }

        [TestMethod, ExpectedException(typeof (ArgumentNullException))]
        public void SetSendWindow_throws_forNullWindowArgument()
        {
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Rdm, RmConstants.ProtocolType);
            RmSocketExtensions.SetSendWindow(socket, null);
        }
        
        [TestMethod, ExpectedException(typeof(ArgumentNullException))]
        public void SetSendWindow_throwArgumentException_forNullWindow()
        {
            using (Socket socket = CreatePgmSocket())
            {
                RmSocketExtensions.SetSendWindow(socket, null);
            }
        }

        [TestMethod, ExpectedException(typeof(SocketException))]
        public void SetSendWindow_throwsSocketException_beforeBind()
        {
            using (Socket socket = CreatePgmSocket())
            {
                var window = new RmSendWindow {RatePerSec = BinarySize.FromMegaBits(1), WindowLength = TimeSpan.FromSeconds(1)};
                RmSocketExtensions.SetSendWindow(socket, window);
            }
        }

        [TestMethod]
        public void SetSendWindow_succeeds_afterBind()
        {
            using (Socket socket = CreatePgmSocket())
            {
                var window = new RmSendWindow {RatePerSec = BinarySize.FromMegaBits(1), WindowLength = TimeSpan.FromSeconds(1)};
                socket.Bind(AnyEndPoint);
                RmSocketExtensions.SetSendWindow(socket, window);
            }
        }

        [TestMethod, ExpectedException(typeof(SocketException))]
        public void SetSendWindow_throwsSocketException_afterConnect()
        {
            using (Socket socket = CreatePgmSocket())
            {
                var window = new RmSendWindow {RatePerSec = BinarySize.FromMegaBits(1), WindowLength = TimeSpan.FromSeconds(1)};
                socket.Bind(AnyEndPoint);
                socket.Connect(MulticastEndPoint);
                RmSocketExtensions.SetSendWindow(socket, window);
            }
        }

        [TestMethod, ExpectedException(typeof (ArgumentNullException))]
        public void SetSocketOption_throws_forSocket()
        {
            RmSocketExtensions.SetSocketOption(null, RmSocketOptionName.UseFec, false);
        }

        [TestMethod()]
        public void SetSocketOption_succeeds_forIPAddress()
        {
            var hostAddress = Dns.GetHostAddresses(Dns.GetHostName())
                .First(ip => ip.AddressFamily == AddressFamily.InterNetwork);
            using (Socket socket = CreatePgmSocket())
            {
                socket.Bind(AnyEndPoint);
                socket.Connect(MulticastEndPoint);
                socket.SetSocketOption(RmSocketOptionName.SetSendInterface, hostAddress);
            }
        }

        [TestMethod()]
        public void SetSocketOption_succeeds_forBool()
        {
            using (Socket socket = CreatePgmSocket())
            {
                socket.Bind(AnyEndPoint);
                socket.SetSocketOption(RmSocketOptionName.HighSpeedIntranet, true);
            }
        }

        [TestMethod()]
        public void SetSocketOption_succeeds_forInt()
        {
            using (Socket socket = CreatePgmSocket())
            {
                socket.Bind(AnyEndPoint);
                socket.Connect(MulticastEndPoint);
                socket.SetSocketOption(RmSocketOptionName.SetTimeToLive, 2);
            }
        }
    }
}
